﻿#region Copyright information
// 
// Copyright © 2005-2013 Yongkee Cho. All rights reserved.
// 
// This code is a part of the Biological Object Library and governed under the terms of the
// GNU Lesser General  Public License (LGPL) version 2.1 which accompanies this distribution.
// For more information on the LGPL, please visit http://bol.codeplex.com/license.
// 
// - Filename: Sequence.cs
// - Author: Yongkee Cho
// - Email: yongkeecho@gmail.com
// - Date Created: 2013-01-25 3:25 PM
// - Last Modified: 2013-01-25 3:59 PM
// 
#endregion
using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.Serialization;

namespace BOL.Models.Seq
{
    /// <summary>
    /// Sequnece class
    /// </summary>
    [DataContract]
    public class Sequence<T> : ISequence<T>
    {
        #region private variables

        private T[] _sequence;

        #endregion

        #region Public properties

        /// <summary>Gets the length of sequence.</summary>
        public int Length { get { return _sequence.Length; } }

        /// <summary>Gets or sets the sequence direction.</summary>
        public Direction Direction { get; private set; }

        #endregion

        #region Constructors

        public Sequence(T[] sequence, Direction direction = Direction.Forward)
        {
            _sequence = sequence;
            Direction = direction;
        }

        public Sequence(IEnumerable<T> sequence, Direction direction = Direction.Forward)
            : this(sequence.ToArray(), direction) { }

        public Sequence(int length, Direction direction = Direction.Forward)
        {
            if (length < 0)
                throw new ArgumentOutOfRangeException("length");

            _sequence = new T[length];
            Direction = direction;
        }

        public Sequence(Direction direction = Direction.Forward)
            : this(new T[0], direction) { }
 
        #endregion

        #region ICloneable implementation

        public Sequence<T> Clone()
        {
            return new Sequence<T>(_sequence.Select(x => x).ToArray(), Direction);
        }

        object ICloneable.Clone()
        {
            return Clone();
        }

        #endregion

        #region Public methods

        /// <summary>
        /// Subsequence this sequence. 
        /// </summary>
        /// <param name="startIndex">The index of the start of the subsequence.</param>
        /// <param name="length">The number of characters in the subsequence. </param>
        /// <returns>Subsequence.</returns>
        public ISequence<T> Subsequence(int startIndex, int length)
        {
            return new Sequence<T>(_sequence.Skip(startIndex).Take(length), Direction);
        }

        /// <summary>
        /// Subsequence this sequence. 
        /// </summary>
        /// <param name="startIndex">The index of the start of the subsequence.</param>
        /// <returns>Subsequence.</returns>
        public ISequence<T> Subsequence(int startIndex)
        {
            return Subsequence(startIndex, Length - startIndex);
        }

        /// <summary>
        /// Removes all occurrences of gaps from the beginning and end of this sequence. 
        /// </summary>
        /// <returns>Trimed sequence.</returns>
        public ISequence<T> Trim(T gap)
        {
            return new Sequence<T>(_sequence.SkipWhile(x => x.Equals(gap)).Select(x => x).Reverse().SkipWhile(x => x.Equals(default(T))).Select(x => x).Reverse().ToArray(), Direction);
        }

        /// <summary>
        /// Removes all occurrences of gaps from the beginning of this sequence.
        /// </summary>
        /// <returns>Trimed sequence.</returns>
        public ISequence<T> TrimStart(T gap)
        {
            return new Sequence<T>(_sequence.SkipWhile(x => x.Equals(gap)).Select(x => x).ToArray(), Direction);
        }

        /// <summary>
        /// Removes all occurrences of gaps from the end of this sequence.
        /// </summary>
        /// <returns>Trimed sequence.</returns>
        public ISequence<T> TrimEnd(T gap)
        {
            return new Sequence<T>(_sequence.Reverse().SkipWhile(x => x.Equals(gap)).Select(x => x).Reverse().ToArray(), Direction);
        }

        /// <summary>
        /// Right-aligns the bases in this sequence, padding on the left with gaps.
        /// </summary>
        /// <param name="item">The item to insert left.</param>
        /// <param name="width">The number of items in the resulting sequence.</param>
        /// <returns>Left-padded sequence.</returns>
        public ISequence<T> PadLeft(T item, int width)
        {
            return new Sequence<T>(Enumerable.Repeat(item, width).Concat(_sequence).ToArray(), Direction);
        }

        public ISequence<T> PadLeft(int width)
        {
            return PadLeft(default(T), width);
        }

        /// <summary>
        /// Left-aligns the bases in this sequence, padding on the right with gaps.
        /// </summary>
        /// <param name="item">The item to insert right.</param>
        /// <param name="width">The number of items in the resulting sequence.</param>
        /// <returns>Right-padded sequence.</returns>
        public ISequence<T> PadRight(T item, int width)
        {
            return new Sequence<T>(_sequence.Concat(Enumerable.Repeat(item, width)), Direction);
        }

        public ISequence<T> PadRight(int width)
        {
            return PadRight(default(T), width);
        }

        /// <summary>Creates an array from a ISequence&lt;/T&gt;.</summary>
        public T[] ToArray()
        {
            return _sequence.ToArray();
        }

        public void Reverse()
        {
            _sequence = _sequence.Reverse().ToArray();

            switch (Direction)
            {
                case Direction.Forward: Direction = Direction.Backward; break;
                case Direction.Backward: Direction = Direction.Forward; break;
            }
        }

        #endregion

        #region IEnumerable<T> implementation

        IEnumerator<T> IEnumerable<T>.GetEnumerator()
        {
            return ((IEnumerable<T>)_sequence).GetEnumerator();
        }

        IEnumerator IEnumerable.GetEnumerator()
        {
            return _sequence.GetEnumerator();
        }

        #endregion

        #region IEquatable<ISequence<T>> implementation

        public bool Equals(ISequence<T> other)
        {
            if (ReferenceEquals(this, other))
                return true;

            if (other == null)
                return false;

            if (Length != other.Length)
                return false;

            var comparer = EqualityComparer<T>.Default;
            var oSeq = other.ToArray();

            for (var i = 0; i < Length; i++)
                if (!comparer.Equals(_sequence[i], oSeq[i]))
                    return false;

            return true;
        }

        #endregion

        #region Object overriden

        public override int GetHashCode()
        {
            return this.Aggregate(1, (hashCode, x) => hashCode ^ x.GetHashCode());
            
            //return ToString().GetHashCode();
        }

        public override bool Equals(object obj)
        {
            if (!(obj is ISequence<T>))
                throw new InvalidCastException("The 'obj' argument is not an ISequence<T> object.");

            return Equals(obj as ISequence<T>);
        }

        /// <summary>
        /// Returns a string that represents the current Sequence. 
        /// </summary>
        /// <returns>A string that represents the current Sequence. </returns>
        public override string ToString()
        {
            return String.Concat(_sequence);
        }

        #endregion
    }
}
